// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package ld

import (
	"cmd/internal/goobj"
	"cmd/internal/objabi"
	"cmd/internal/sys"
	"cmd/link/internal/loader"
	"cmd/link/internal/sym"
	"fmt"
	"internal/buildcfg"
	"os"
	"path/filepath"
)

// pclntab保存生成pclntab所需的状态。
type pclntab struct {
	// 运行时中func对象的大小。
	funcSize uint32

	// 找到的第一个和最后一个函数。
	firstFunc, lastFunc loader.Sym

	// 运行pclntab的总大小。
	size int64

	// runtime.pclntab的符号
	carrier     loader.Sym
	pclntab     loader.Sym
	pcheader    loader.Sym
	funcnametab loader.Sym
	findfunctab loader.Sym
	cutab       loader.Sym
	filetab     loader.Sym
	pctab       loader.Sym

	// 函数数+文本节数-1。这就是这样一个问题
	// 意外值，因为具有多个文本节的平台
	// 获取在之间插入的伪函数，因为外部链接器可以放置
	// 这些领域的职能。我们将这些区域标记为Go未涵盖的区域
	// 运行时。
	// None
	// 在大多数平台上，这是可访问函数的数量。
	nfunc int32

	// runtime.filetab中的文件名数。
	nfiles uint32
}

// addGeneratedSym将生成器符号添加到pclntab，返回新符号。
// 呼叫方有责任将其符号保存在状态中。
func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f generatorFunc) loader.Sym {
	size = Rnd(size, int64(ctxt.Arch.PtrSize))
	state.size += size
	s := ctxt.createGeneratorSymbol(name, 0, sym.SPCLNTAB, size, f)
	ctxt.loader.SetAttrReachable(s, true)
	ctxt.loader.SetCarrierSym(s, state.carrier)
	ctxt.loader.SetAttrNotInSymbolTable(s, true)
	return s
}

// makePclntab生成一个pclntab对象，并组装所有编译单元
// 我们需要写pclntab。返回pclntab结构，即
// 我们需要的编译单元，以及我们需要的一部分函数符号
// 生成pclntab。
func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit, []loader.Sym) {
	ldr := ctxt.loader

	state := &pclntab{
		// 这是runtime/runtime2.go中_func对象的大小。
		funcSize: uint32(ctxt.Arch.PtrSize + 9*4),
	}

	// 收集一些基本的统计数据和信息。
	seenCUs := make(map[*sym.CompilationUnit]struct{})
	prevSect := ldr.SymSect(ctxt.Textp[0])
	compUnits := []*sym.CompilationUnit{}
	funcs := []loader.Sym{}

	for _, s := range ctxt.Textp {
		if !emitPcln(ctxt, s, container) {
			continue
		}
		funcs = append(funcs, s)
		state.nfunc++
		if state.firstFunc == 0 {
			state.firstFunc = s
		}
		state.lastFunc = s
		ss := ldr.SymSect(s)
		if ss != prevSect {
			// 对于多个文本节，外部链接器可以
			// 在节之间插入函数，这些节不是
			// 以围棋闻名。这将在覆盖的PC范围内留下漏洞
			// 通过func表。我们需要生成一个条目来标记
			// 这个洞。
			state.nfunc++
			prevSect = ss
		}

		// 我们需要跟踪我们看到的所有编译单元。一些符号
		// （例如，go.buildid、\u cgoexp\u等）将没有编译单元。
		cu := ldr.SymUnit(s)
		if _, ok := seenCUs[cu]; cu != nil && !ok {
			seenCUs[cu] = struct{}{}
			cu.PclnIndex = len(compUnits)
			compUnits = append(compUnits, cu)
		}
	}
	return state, compUnits, funcs
}

func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool {
	// 我们只想为“最低”生成func表项
	// “级别”符号，而不是子符号的容器。
	return !container.Has(s)
}

func computeDeferReturn(ctxt *Link, deferReturnSym, s loader.Sym) uint32 {
	ldr := ctxt.loader
	target := ctxt.Target
	deferreturn := uint32(0)
	lastWasmAddr := uint32(0)

	relocs := ldr.Relocs(s)
	for ri := 0; ri < relocs.Count(); ri++ {
		r := relocs.At(ri)
		if target.IsWasm() && r.Type() == objabi.R_ADDR {
			// Wasm在返回时未设置活动变量
			// 自称。相反，它有一个由
			// 紧接返回之前的恢复点。
			// wasm代码有一个R_ADDR重定位，用于
			// 将恢复点设置为PC_B。
			lastWasmAddr = uint32(r.Add())
		}
		if r.Type().IsDirectCall() && (r.Sym() == deferReturnSym || ldr.IsDeferReturnTramp(r.Sym())) {
			if target.IsWasm() {
				deferreturn = lastWasmAddr - 1
			} else {
				// 注意：重新定位目标在call指令中，但是
				// 不一定是整个指令（例如，在
				// x86重新定位适用于5字节调用中的字节[1:5]
				// 指示）。
				deferreturn = uint32(r.Off())
				switch target.Arch.Family {
				case sys.AMD64, sys.I386:
					deferreturn--
				case sys.PPC64, sys.ARM, sys.ARM64, sys.MIPS, sys.MIPS64:
					// 无变化
				case sys.RISCV64:
					// TODO（jsing）：JALR指令标记为
					// R_CALLRISCV，而实际的reloc当前为
					// 以AUIPC开头的一条指令。
					deferreturn -= 4
				case sys.S390X:
					deferreturn -= 2
				default:
					panic(fmt.Sprint("Unhandled architecture:", target.Arch.Family))
				}
			}
			break // 只需要一个
		}
	}
	return deferreturn
}

// genInlTreeSym为具有
// 指定的FuncInfo。
func genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, nameOffsets map[loader.Sym]uint32) loader.Sym {
	ldr := ctxt.loader
	its := ldr.CreateExtSym("", 0)
	inlTreeSym := ldr.MakeSymbolUpdater(its)
	// 注意：生成的符号的类型为sym.SGOFUNC，作为
	// 向symtab（）阶段发送需要分组的信号
	// 其他类似符号（gcdata等）；dodata（）阶段将
	// 最终将类型切换回SRODATA。
	inlTreeSym.SetType(sym.SGOFUNC)
	ldr.SetAttrReachable(its, true)
	ninl := fi.NumInlTree()
	for i := 0; i < int(ninl); i++ {
		call := fi.InlTree(i)
		val := call.File
		nameoff, ok := nameOffsets[call.Func]
		if !ok {
			panic("couldn't find function name offset")
		}

		inlTreeSym.SetUint16(arch, int64(i*20+0), uint16(call.Parent))
		inlFunc := ldr.FuncInfo(call.Func)

		var funcID objabi.FuncID
		if inlFunc.Valid() {
			funcID = inlFunc.FuncID()
		}
		inlTreeSym.SetUint8(arch, int64(i*20+2), uint8(funcID))

		// 字节3未使用
		inlTreeSym.SetUint32(arch, int64(i*20+4), uint32(val))
		inlTreeSym.SetUint32(arch, int64(i*20+8), uint32(call.Line))
		inlTreeSym.SetUint32(arch, int64(i*20+12), uint32(nameoff))
		inlTreeSym.SetUint32(arch, int64(i*20+16), uint32(call.ParentPC))
	}
	return its
}

// makeInlSyms返回由inlSyms创建的loader.Sym的映射。
func makeInlSyms(ctxt *Link, funcs []loader.Sym, nameOffsets map[loader.Sym]uint32) map[loader.Sym]loader.Sym {
	ldr := ctxt.loader
	// 创建我们需要的内联符号。
	inlSyms := make(map[loader.Sym]loader.Sym)
	for _, s := range funcs {
		if fi := ldr.FuncInfo(s); fi.Valid() {
			fi.Preload()
			if fi.NumInlTree() > 0 {
				inlSyms[s] = genInlTreeSym(ctxt, ldr.SymUnit(s), fi, ctxt.Arch, nameOffsets)
			}
		}
	}
	return inlSyms
}

// generatePCHeader创建runtime.pcheader符号，将其设置为
// 生成器，以便稍后填充其数据。
func (state *pclntab) generatePCHeader(ctxt *Link) {
	writeHeader := func(ctxt *Link, s loader.Sym) {
		ldr := ctxt.loader
		header := ctxt.loader.MakeSymbolUpdater(s)

		writeSymOffset := func(off int64, ws loader.Sym) int64 {
			diff := ldr.SymValue(ws) - ldr.SymValue(s)
			if diff <= 0 {
				name := ldr.SymName(ws)
				panic(fmt.Sprintf("expected runtime.pcheader(%x) to be placed before %s(%x)", ldr.SymValue(s), name, ldr.SymValue(ws)))
			}
			return header.SetUintptr(ctxt.Arch, off, uintptr(diff))
		}

		// 写入标题。
		// 与runtime/symtab保持同步。转到：pcHeader。
		header.SetUint32(ctxt.Arch, 0, 0xfffffffa)
		header.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC))
		header.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize))
		off := header.SetUint(ctxt.Arch, 8, uint64(state.nfunc))
		off = header.SetUint(ctxt.Arch, off, uint64(state.nfiles))
		off = writeSymOffset(off, state.funcnametab)
		off = writeSymOffset(off, state.cutab)
		off = writeSymOffset(off, state.filetab)
		off = writeSymOffset(off, state.pctab)
		off = writeSymOffset(off, state.pclntab)
	}

	size := int64(8 + 7*ctxt.Arch.PtrSize)
	state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
}

// walkFuncs迭代函数，为每个唯一的函数调用一个函数
// 函数和内联函数。
func walkFuncs(ctxt *Link, funcs []loader.Sym, f func(loader.Sym)) {
	ldr := ctxt.loader
	seen := make(map[loader.Sym]struct{})
	for _, s := range funcs {
		if _, ok := seen[s]; !ok {
			f(s)
			seen[s] = struct{}{}
		}

		fi := ldr.FuncInfo(s)
		if !fi.Valid() {
			continue
		}
		fi.Preload()
		for i, ni := 0, fi.NumInlTree(); i < int(ni); i++ {
			call := fi.InlTree(i).Func
			if _, ok := seen[call]; !ok {
				f(call)
				seen[call] = struct{}{}
			}
		}
	}
}

// generateFuncnametab创建函数名表。返回
// func符号到runtime.funcnamtab中的名称偏移量。
func (state *pclntab) generateFuncnametab(ctxt *Link, funcs []loader.Sym) map[loader.Sym]uint32 {
	nameOffsets := make(map[loader.Sym]uint32, state.nfunc)

	// 写入以null结尾的字符串。
	writeFuncNameTab := func(ctxt *Link, s loader.Sym) {
		symtab := ctxt.loader.MakeSymbolUpdater(s)
		for s, off := range nameOffsets {
			symtab.AddStringAt(int64(off), ctxt.loader.SymName(s))
		}
	}

	// 循环通过CU，并计算所需的大小。
	var size int64
	walkFuncs(ctxt, funcs, func(s loader.Sym) {
		nameOffsets[s] = uint32(size)
		size += int64(ctxt.loader.SymNameLen(s)) + 1 // 空终止
	})

	state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab)
	return nameOffsets
}

// walkFilenames遍历func，为每个函数中使用的每个文件名调用一个函数
// 函数的行表。
func walkFilenames(ctxt *Link, funcs []loader.Sym, f func(*sym.CompilationUnit, goobj.CUFileIndex)) {
	ldr := ctxt.loader

	// 循环所有函数，查找我们需要的文件名。
	for _, s := range funcs {
		fi := ldr.FuncInfo(s)
		if !fi.Valid() {
			continue
		}
		fi.Preload()

		cu := ldr.SymUnit(s)
		for i, nf := 0, int(fi.NumFile()); i < nf; i++ {
			f(cu, fi.File(i))
		}
		for i, ninl := 0, int(fi.NumInlTree()); i < ninl; i++ {
			call := fi.InlTree(i)
			f(cu, call.File)
		}
	}
}

// generateFilenameTabs创建文件名查找所需的LUT。返回一个切片
// 每个CU在runtime.cutab中开始的索引的。
// None
// 函数对象跟踪它们在打印堆栈时引用的文件。
// 如果CU[M]引用，此函数将创建每个CU的文件名列表
// 文件[1-N]，生成以下内容：
// None
// runtime.cutab：
// CU[M]
// OffsetOffileName[0]
// OffsetOffileName[1]
// ..
// None
// runtime.filetab
// 文件名[0]
// 文件名[1]
// None
// 查找文件名时会变成：
// 0）给定func和文件名索引[K]
// 1） 获取Func.CUIndex:M:=Func.cuOffset
// 2） 查找文件名偏移量：fileOffset:=runtime.cutab[M+K]
// 3） 获取文件名：getcstring（runtime.filetab[fileOffset]）
func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, funcs []loader.Sym) []uint32 {
	// 在每个CU的基础上，跟踪我们需要的所有文件名。
	// None
	// 注意，我们将文件名存储在对象的一个单独部分中
	// 文件，并根据实际值执行重复数据消除。最好是
	// 使用内容可寻址符号（和）将文件名存储为符号
	// 然后不加载额外的文件名），只使用
	// 用于进行此编目的符号名称。
	// None
	// TODO:将文件名存储为符号。（注意，如果您
	// 还将字符串移动到始终使用较大的内容可寻址哈希
	// 函数，并使用该哈希值进行唯一性测试。）
	cuEntries := make([]goobj.CUFileIndex, len(compUnits))
	fileOffsets := make(map[string]uint32)

	// 浏览文件名。
	// 我们存储需要加载的文件名字符串总长度和最大值
	// 我们已经看到每个CU的文件索引，因此我们可以计算
	// CU->需要修改全局表。
	var fileSize int64
	walkFilenames(ctxt, funcs, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) {
		// 注意：我们使用原始文件名进行查找，但使用扩展文件名
		// 当我们保存尺寸时。
		filename := cu.FileTable[i]
		if _, ok := fileOffsets[filename]; !ok {
			fileOffsets[filename] = uint32(fileSize)
			fileSize += int64(len(expandFile(filename)) + 1) // 空终止
		}

		// 找到我们看到的最大文件索引。
		if cuEntries[cu.PclnIndex] < i+1 {
			cuEntries[cu.PclnIndex] = i + 1 // 存储最大值+1
		}
	})

	// 计算runtime.cutab变量的大小。
	var totalEntries uint32
	cuOffsets := make([]uint32, len(cuEntries))
	for i, entries := range cuEntries {
		// 注意，cutab是uint32的一个切片，因此cu条目的偏移量就是
		// 运行到目前为止我们需要存储的所有cu索引的总和，而不是
		// 到目前为止我们存储的字节数。
		cuOffsets[i] = totalEntries
		totalEntries += uint32(entries)
	}

	// 写下cutab。
	writeCutab := func(ctxt *Link, s loader.Sym) {
		sb := ctxt.loader.MakeSymbolUpdater(s)

		var off int64
		for i, max := range cuEntries {
			// 写入每CU LUT。
			cu := compUnits[i]
			for j := goobj.CUFileIndex(0); j < max; j++ {
				fileOffset, ok := fileOffsets[cu.FileTable[j]]
				if !ok {
					// 我们正在遍历所有可能的文件索引。有可能是一个文件
					// 已经消除了死代码，虽然它是CU中的有效文件，但是
					// 在这个二进制文件中不需要。发生这种情况时，请使用无效的偏移量。
					fileOffset = ^uint32(0)
				}
				off = sb.SetUint32(ctxt.Arch, off, fileOffset)
			}
		}
	}
	state.cutab = state.addGeneratedSym(ctxt, "runtime.cutab", int64(totalEntries*4), writeCutab)

	// 写入文件选项卡。
	writeFiletab := func(ctxt *Link, s loader.Sym) {
		sb := ctxt.loader.MakeSymbolUpdater(s)

		// 写字符串。
		for filename, loc := range fileOffsets {
			sb.AddStringAt(int64(loc), expandFile(filename))
		}
	}
	state.nfiles = uint32(len(fileOffsets))
	state.filetab = state.addGeneratedSym(ctxt, "runtime.filetab", fileSize, writeFiletab)

	return cuOffsets
}

// generatePctab创建runtime.pctab变量，其中包含所有
// 已消除重复数据的pcdata。
func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) {
	ldr := ctxt.loader

	// 0的Pctab偏移在运行时被视为无效。我们尊重
	// 只需在runtime.pctab的开头填充一个字节，
	// 这样，任何实际偏移量都不能为零。
	size := int64(1)

	// 遍历函数，查找偏移量以存储每个pcdata。
	seen := make(map[loader.Sym]struct{})
	saveOffset := func(pcSym loader.Sym) {
		if _, ok := seen[pcSym]; !ok {
			datSize := ldr.SymSize(pcSym)
			if datSize != 0 {
				ldr.SetSymValue(pcSym, size)
			} else {
				// 无效的PC数据，记录为零。
				ldr.SetSymValue(pcSym, 0)
			}
			size += datSize
			seen[pcSym] = struct{}{}
		}
	}
	for _, s := range funcs {
		fi := ldr.FuncInfo(s)
		if !fi.Valid() {
			continue
		}
		fi.Preload()

		pcSyms := []loader.Sym{fi.Pcsp(), fi.Pcfile(), fi.Pcline()}
		for _, pcSym := range pcSyms {
			saveOffset(pcSym)
		}
		for _, pcSym := range fi.Pcdata() {
			saveOffset(pcSym)
		}
		if fi.NumInlTree() > 0 {
			saveOffset(fi.Pcinline())
		}
	}

	// TODO:没有理由我们需要一个用于此变量的生成器，而且
	// 可以移动到载体符号。然而，载波符号包含
	// 运营商符号尚未生效（截至2020年8月）。一旦这个问题解决了，
	// runtime.pctab可能只是一个载波符号。
	writePctab := func(ctxt *Link, s loader.Sym) {
		ldr := ctxt.loader
		sb := ldr.MakeSymbolUpdater(s)
		for sym := range seen {
			sb.SetBytesAt(ldr.SymValue(sym), ldr.Data(sym))
		}
	}

	state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab)
}

// numPCData返回FuncInfo的PCData符号数。
// 注意：在调用此函数之前，必须对有效的FUNCINFO调用Preload。
func numPCData(fi loader.FuncInfo) uint32 {
	if !fi.Valid() {
		return 0
	}
	numPCData := uint32(len(fi.Pcdata()))
	if fi.NumInlTree() > 0 {
		if numPCData < objabi.PCDATA_InlTreeIndex+1 {
			numPCData = objabi.PCDATA_InlTreeIndex + 1
		}
	}
	return numPCData
}

// 用于迭代pclntab的帮助器类型。
type pclnSetAddr func(*loader.SymbolBuilder, *sys.Arch, int64, loader.Sym, int64) int64
type pclnSetUint func(*loader.SymbolBuilder, *sys.Arch, int64, uint64) int64

// generateFunctab创建runtime.functab
// None
// runtime.functab包含两个内容：
// None
// -pc->func查找表。
// -func对象数组，与pcdata和funcdata交错
// None
// 由于链接器中的定时，生成此表需要两次过程。
// 第一个过程在链接的早期执行，它创建任何需要的
// 重新定位以布局数据。需要重新定位的部件包括：
// 1） PC->func表。
// 2） func对象中的入口点。
// 3） 函数数据。
// （1） 和（2）在walkPCToFunc中处理。（3） 在walkFuncdata中处理。
// None
// 重新定位后，一旦我们知道在输出缓冲区中写入内容的位置，
// 我们执行第二个过程，实际上是写入数据。
func (state *pclntab) generateFunctab(ctxt *Link, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) {
	// 计算表的大小。
	size, startLocations := state.calculateFunctabSize(ctxt, funcs)

	// 如果我们在内部链接一个静态可执行文件，那么函数将
	// 是已知的，所以我们可以使用它们，而不是重新定位。对于
	// 在其他情况下，我们仍然需要重新定位。
	// None
	// 这个布尔值只是帮助我们确定要使用哪个回调。
	useSymValue := ctxt.IsExe() && ctxt.IsInternal()

	writePcln := func(ctxt *Link, s loader.Sym) {
		ldr := ctxt.loader
		sb := ldr.MakeSymbolUpdater(s)

		// 创建我们的回调。
		var setAddr pclnSetAddr
		if useSymValue {
			// 我们需要写偏移量。
			setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 {
				if v := ldr.SymValue(tgt); v != 0 {
					s.SetUint(arch, off, uint64(v+add))
				}
				return 0
			}
		} else {
			// 我们已经写了搬迁。
			setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { return 0 }
		}

		// 写下数据。
		writePcToFunc(ctxt, sb, funcs, startLocations, setAddr, (*loader.SymbolBuilder).SetUint)
		writeFuncs(ctxt, sb, funcs, inlSyms, startLocations, cuOffsets, nameOffsets)
		state.writeFuncData(ctxt, sb, funcs, inlSyms, startLocations, setAddr, (*loader.SymbolBuilder).SetUint)
	}

	state.pclntab = state.addGeneratedSym(ctxt, "runtime.functab", size, writePcln)

	// 创建我们需要的重新定位。
	ldr := ctxt.loader
	sb := ldr.MakeSymbolUpdater(state.pclntab)

	var setAddr pclnSetAddr
	if useSymValue {
		// 如果我们应该使用符号值，而我们没有符号值，那么写一个重定位。
		setAddr = func(sb *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 {
			if v := ldr.SymValue(tgt); v == 0 {
				sb.SetAddrPlus(arch, off, tgt, add)
			}
			return 0
		}
	} else {
		// 如果我们是外部链接，写一个重新定位。
		setAddr = (*loader.SymbolBuilder).SetAddrPlus
	}
	setUintNOP := func(*loader.SymbolBuilder, *sys.Arch, int64, uint64) int64 { return 0 }
	writePcToFunc(ctxt, sb, funcs, startLocations, setAddr, setUintNOP)
	if !useSymValue {
		// 外部链接时为funcdata生成重定位。
		state.writeFuncData(ctxt, sb, funcs, inlSyms, startLocations, setAddr, setUintNOP)
		sb.SortRelocs()
	}
}

// funcData返回FuncInfo的funcData和偏移量。
// funcdata和偏移量在每个func之后写入runtime.functab
// 对象这是一个帮助函数，用于查询FuncInfo对象
// 清洁工
// None
// 注意，大多数FDOFFSET为0，这意味着两者之间没有偏移
// 编译器生成的符号，以及运行时需要什么。他们是
// 为不丧失概括性而进行了深入研究。
// None
// 注意：在调用之前，必须对FuncInfo调用Preload。
// 注意：FDSYM和FDOFF用作暂存空间。
func funcData(fi loader.FuncInfo, inlSym loader.Sym, fdSyms []loader.Sym, fdOffs []int64) ([]loader.Sym, []int64) {
	fdSyms, fdOffs = fdSyms[:0], fdOffs[:0]
	if fi.Valid() {
		numOffsets := int(fi.NumFuncdataoff())
		for i := 0; i < numOffsets; i++ {
			fdOffs = append(fdOffs, fi.Funcdataoff(i))
		}
		fdSyms = fi.Funcdata(fdSyms)
		if fi.NumInlTree() > 0 {
			if len(fdSyms) < objabi.FUNCDATA_InlTree+1 {
				fdSyms = append(fdSyms, make([]loader.Sym, objabi.FUNCDATA_InlTree+1-len(fdSyms))...)
				fdOffs = append(fdOffs, make([]int64, objabi.FUNCDATA_InlTree+1-len(fdOffs))...)
			}
			fdSyms[objabi.FUNCDATA_InlTree] = inlSym
		}
	}
	return fdSyms, fdOffs
}

// CalculateFunctableSize计算pclntab的大小以及中的偏移量
// 单个func项的输出缓冲区。
func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64, []uint32) {
	ldr := ctxt.loader
	startLocations := make([]uint32, len(funcs))

	// 为pc->func表分配空间。该结构由一台pc机组成
	// 和func结构的偏移量。之后，我们就有了一台个人电脑
	// 值，该值标记二进制文件中最后一个函数的结束。
	size := int64(int(state.nfunc)*2*ctxt.Arch.PtrSize + ctxt.Arch.PtrSize)

	// 现在找到func对象的空间。我们是以连续的方式进行的，
	// 这样我们就可以找到单独的起始位置，因为
	// 需要对齐。
	for i, s := range funcs {
		size = Rnd(size, int64(ctxt.Arch.PtrSize))
		startLocations[i] = uint32(size)
		fi := ldr.FuncInfo(s)
		size += int64(state.funcSize)
		if fi.Valid() {
			fi.Preload()
			numFuncData := int(fi.NumFuncdataoff())
			if fi.NumInlTree() > 0 {
				if numFuncData < objabi.FUNCDATA_InlTree+1 {
					numFuncData = objabi.FUNCDATA_InlTree + 1
				}
			}
			size += int64(numPCData(fi) * 4)
			if numFuncData > 0 { // Func数据已对齐。
				size = Rnd(size, int64(ctxt.Arch.PtrSize))
			}
			size += int64(numFuncData * ctxt.Arch.PtrSize)
		}
	}

	return size, startLocations
}

// writePcToFunc写入PC->func查找表。
// 此函数用于遍历pc->func查找表，执行回调
// 生成重定位并写入表的值。
func writePcToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, startLocations []uint32, setAddr pclnSetAddr, setUint pclnSetUint) {
	ldr := ctxt.loader
	var prevFunc loader.Sym
	prevSect := ldr.SymSect(funcs[0])
	funcIndex := 0
	for i, s := range funcs {
		if thisSect := ldr.SymSect(s); thisSect != prevSect {
			// 如果有多个文本部分，则可能会有一个洞
			// 地址空间。我们使用无效的funcoff值来标记孔。
			// 另请参见runtime/symtab.go:findfunc
			prevFuncSize := int64(ldr.SymSize(prevFunc))
			setAddr(sb, ctxt.Arch, int64(funcIndex*2*ctxt.Arch.PtrSize), prevFunc, prevFuncSize)
			setUint(sb, ctxt.Arch, int64((funcIndex*2+1)*ctxt.Arch.PtrSize), ^uint64(0))
			funcIndex++
			prevSect = thisSect
		}
		prevFunc = s
		// 托多：我们实际上不需要这些搬迁，只要我们去一个
		// module->func查找表，就像我们查找文件名一样。我们可以喝一杯
		// 对模块进行一次重新定位，并将其全部布置为
		// 从该模块开始的偏移量。
		setAddr(sb, ctxt.Arch, int64(funcIndex*2*ctxt.Arch.PtrSize), s, 0)
		setUint(sb, ctxt.Arch, int64((funcIndex*2+1)*ctxt.Arch.PtrSize), uint64(startLocations[i]))
		funcIndex++

		// 写入条目位置。
		setAddr(sb, ctxt.Arch, int64(startLocations[i]), s, 0)
	}

	// 表格的最后一项是结束pc。
	setAddr(sb, ctxt.Arch, int64(funcIndex)*2*int64(ctxt.Arch.PtrSize), prevFunc, ldr.SymSize(prevFunc))
}

// writeFuncData写入funcdata表。
// None
// 此函数为中所需的每个funcdata执行回调
// runtime.functab。对于内部链接的静态文件，应该调用一次
// 二进制文件，或两次（一次生成所需的重新定位）用于其他
// 构建模式。
// None
// 注意，此函数的输出与writeFuncs交织在一起，但这是
// 一个单独的函数，因为在不同的传递中需要它
// generateFunctab。
func (state *pclntab) writeFuncData(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations []uint32, setAddr pclnSetAddr, setUint pclnSetUint) {
	ldr := ctxt.loader
	funcdata, funcdataoff := []loader.Sym{}, []int64{}
	for i, s := range funcs {
		fi := ldr.FuncInfo(s)
		if !fi.Valid() {
			continue
		}
		fi.Preload()

		// funcdata，必须与指针对齐，并且我们仅与int32对齐。
		// 缺少的数据将是0（零指针）。
		funcdata, funcdataoff := funcData(fi, inlSyms[s], funcdata, funcdataoff)
		if len(funcdata) > 0 {
			off := int64(startLocations[i] + state.funcSize + numPCData(fi)*4)
			off = Rnd(off, int64(ctxt.Arch.PtrSize))
			for j := range funcdata {
				dataoff := off + int64(ctxt.Arch.PtrSize*j)
				if funcdata[j] == 0 {
					setUint(sb, ctxt.Arch, dataoff, uint64(funcdataoff[j]))
					continue
				}
				// TODO:这是否需要重复数据消除？
				setAddr(sb, ctxt.Arch, dataoff, funcdata[j], funcdataoff[j])
			}
		}
	}
}

// writeFuncs将func结构和pcdata写入runtime.functab。
func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) {
	ldr := ctxt.loader
	deferReturnSym := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal)
	funcdata, funcdataoff := []loader.Sym{}, []int64{}

	// 写入各个func对象。
	for i, s := range funcs {
		fi := ldr.FuncInfo(s)
		if fi.Valid() {
			fi.Preload()
		}

		// 注意，我们跳过了输入值的空格——这是在inn中处理的
		// walkPCToFunc。我们不把它写在这里，因为它可能需要一个
		// 重新安置
		off := startLocations[i] + uint32(ctxt.Arch.PtrSize) // 进入

		// 名称int32
		nameoff, ok := nameOffsets[s]
		if !ok {
			panic("couldn't find function name offset")
		}
		off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(nameoff)))

		// args int32
		// TODO:进入funcinfo。
		args := uint32(0)
		if fi.Valid() {
			args = uint32(fi.Args())
		}
		off = uint32(sb.SetUint32(ctxt.Arch, int64(off), args))

		// 延迟回报
		deferreturn := computeDeferReturn(ctxt, deferReturnSym, s)
		off = uint32(sb.SetUint32(ctxt.Arch, int64(off), deferreturn))

		// pcdata
		if fi.Valid() {
			off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcsp()))))
			off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcfile()))))
			off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcline()))))
		} else {
			off += 12
		}
		off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(numPCData(fi))))

		// 将偏移量存储到编译单元的文件表中。
		cuIdx := ^uint32(0)
		if cu := ldr.SymUnit(s); cu != nil {
			cuIdx = cuOffsets[cu.PclnIndex]
		}
		off = uint32(sb.SetUint32(ctxt.Arch, int64(off), cuIdx))

		// functid uint8
		var funcID objabi.FuncID
		if fi.Valid() {
			funcID = fi.FuncID()
		}
		off = uint32(sb.SetUint8(ctxt.Arch, int64(off), uint8(funcID)))

		// 标志uint8
		var flag objabi.FuncFlag
		if fi.Valid() {
			flag = fi.FuncFlag()
		}
		off = uint32(sb.SetUint8(ctxt.Arch, int64(off), uint8(flag)))

		off += 1 // 衬垫

		// nfuncdata必须是最终条目。
		funcdata, funcdataoff = funcData(fi, 0, funcdata, funcdataoff)
		off = uint32(sb.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata))))

		// 输出pcdata。
		if fi.Valid() {
			for j, pcSym := range fi.Pcdata() {
				sb.SetUint32(ctxt.Arch, int64(off+uint32(j*4)), uint32(ldr.SymValue(pcSym)))
			}
			if fi.NumInlTree() > 0 {
				sb.SetUint32(ctxt.Arch, int64(off+objabi.PCDATA_InlTreeIndex*4), uint32(ldr.SymValue(fi.Pcinline())))
			}
		}
	}
}

// pclntab使用初始化pclntab符号
// 运行时函数和文件名信息。

// pclntab为链接输出生成pcln表。
func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
	// Go 1.2的symtab布局记录在golang.org/s/go12symtab中，但是
	// 从那时起，布局和数据发生了变化。
	// None
	// 截至2020年8月，pclntab的布局如下：
	// None
	// .gopclntab/_gopclntab[极低频段/马赫段]
	// runtime.pclntab
	// 整个pclntab部分的载波符号。
	// None
	// runtime.pcheader（请参阅：runtime/symtab.go:pcheader）
	// 8字节魔术
	// nfunc[thearch.ptrsize字节]
	// 从runtime.pcheader开始到runtime.funcnametab的偏移量
	// 从runtime.pcheader开始到runtime.pclntab的偏移量
	// None
	// runtime.funcnametab
	// []以null结尾的函数名列表
	// None
	// runtime.cutab
	// 对于i=0
	// 对于j=0..#以CU[i]为单位的最大已用文件索引
	// uint32文件名[j]到runtime.filetab的偏移量
	// None
	// runtime.filetab
	// []以null结尾的文件名字符串
	// None
	// runtime.pctab
	// []字节的重复数据消除pc数据。
	// None
	// runtime.functab
	// 函数表，交替PC和偏移量到func结构[每个条目都是arch.ptrsize字节]
	// 结束PC[thearch.ptrsize字节]
	// func结构、pcdata偏移、func数据。

	state, compUnits, funcs := makePclntab(ctxt, container)

	ldr := ctxt.loader
	state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0)
	ldr.MakeSymbolUpdater(state.carrier).SetType(sym.SPCLNTAB)
	ldr.SetAttrReachable(state.carrier, true)
	setCarrierSym(sym.SPCLNTAB, state.carrier)

	state.generatePCHeader(ctxt)
	nameOffsets := state.generateFuncnametab(ctxt, funcs)
	cuOffsets := state.generateFilenameTabs(ctxt, compUnits, funcs)
	state.generatePctab(ctxt, funcs)
	inlSyms := makeInlSyms(ctxt, funcs, nameOffsets)
	state.generateFunctab(ctxt, funcs, inlSyms, cuOffsets, nameOffsets)

	return state
}

func gorootFinal() string {
	root := buildcfg.GOROOT
	if final := os.Getenv("GOROOT_FINAL"); final != "" {
		root = final
	}
	return root
}

func expandGoroot(s string) string {
	const n = len("$GOROOT")
	if len(s) >= n+1 && s[:n] == "$GOROOT" && (s[n] == '/' || s[n] == '\\') {
		return filepath.ToSlash(filepath.Join(gorootFinal(), s[n:]))
	}
	return s
}

const (
	BUCKETSIZE    = 256 * MINFUNC
	SUBBUCKETS    = 16
	SUBBUCKETSIZE = BUCKETSIZE / SUBBUCKETS
	NOIDX         = 0x7fffffff
)

// findfunctab生成查找表以快速查找包含
// 函数。有关详细信息，请参阅src/runtime/symtab.go:findfunc。
func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) {
	ldr := ctxt.loader

	// 查找最小和最大地址
	min := ldr.SymValue(ctxt.Textp[0])
	lastp := ctxt.Textp[len(ctxt.Textp)-1]
	max := ldr.SymValue(lastp) + ldr.SymSize(lastp)

	// 对于每个子bucket，计算所有符号索引的最小值
	// 那张地图指向那个小水桶。
	n := int32((max - min + SUBBUCKETSIZE - 1) / SUBBUCKETSIZE)

	nbuckets := int32((max - min + BUCKETSIZE - 1) / BUCKETSIZE)

	size := 4*int64(nbuckets) + int64(n)

	writeFindFuncTab := func(_ *Link, s loader.Sym) {
		t := ldr.MakeSymbolUpdater(s)

		indexes := make([]int32, n)
		for i := int32(0); i < n; i++ {
			indexes[i] = NOIDX
		}
		idx := int32(0)
		for i, s := range ctxt.Textp {
			if !emitPcln(ctxt, s, container) {
				continue
			}
			p := ldr.SymValue(s)
			var e loader.Sym
			i++
			if i < len(ctxt.Textp) {
				e = ctxt.Textp[i]
			}
			for e != 0 && !emitPcln(ctxt, e, container) && i < len(ctxt.Textp) {
				e = ctxt.Textp[i]
				i++
			}
			q := max
			if e != 0 {
				q = ldr.SymValue(e)
			}

			// 打印（“%d:[%lld%lld]%s\n”，idx，p，q，s->name）；
			for ; p < q; p += SUBBUCKETSIZE {
				i = int((p - min) / SUBBUCKETSIZE)
				if indexes[i] > idx {
					indexes[i] = idx
				}
			}

			i = int((q - 1 - min) / SUBBUCKETSIZE)
			if indexes[i] > idx {
				indexes[i] = idx
			}
			idx++
		}

		// 填表
		for i := int32(0); i < nbuckets; i++ {
			base := indexes[i*SUBBUCKETS]
			if base == NOIDX {
				Errorf(nil, "hole in findfunctab")
			}
			t.SetUint32(ctxt.Arch, int64(i)*(4+SUBBUCKETS), uint32(base))
			for j := int32(0); j < SUBBUCKETS && i*SUBBUCKETS+j < n; j++ {
				idx = indexes[i*SUBBUCKETS+j]
				if idx == NOIDX {
					Errorf(nil, "hole in findfunctab")
				}
				if idx-base >= 256 {
					Errorf(nil, "too many functions in a findfunc bucket! %d/%d %d %d", i, nbuckets, j, idx-base)
				}

				t.SetUint8(ctxt.Arch, int64(i)*(4+SUBBUCKETS)+4+int64(j), uint8(idx-base))
			}
		}
	}

	state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SRODATA, size, writeFindFuncTab)
	ldr.SetAttrReachable(state.findfunctab, true)
	ldr.SetAttrLocal(state.findfunctab, true)
}

// findContainerSyms返回位图，按符号编号索引，其中
// 每个容器符号对应1。
func (ctxt *Link) findContainerSyms() loader.Bitmap {
	ldr := ctxt.loader
	container := loader.MakeBitmap(ldr.NSym())
	// 找到容器符号并将其标记为容器符号。
	for _, s := range ctxt.Textp {
		outer := ldr.OuterSym(s)
		if outer != 0 {
			container.Set(outer)
		}
	}
	return container
}
